package com.msi.algorithm;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.msi.Graph;
import com.msi.Main;
import com.msi.Vertex;

/**
 * Stan częściowy algorytmu CSP
 * 
 * @author Michał Kołodziejski
 */
public class State {

    /**
     * Liczba istniejących stanów
     */
    private static int stateCount = 0;
    
    
    /**
     * Identyfikator stanu
     */
    protected int stateId;
    
    /**
     * Kopia grafu połączeń
     */
    protected Graph graph;

    /**
     * Aktualnie rozpatrywany wierzchołek grafu
     */
    protected final Vertex currentVertex;

    /**
     * Lista stołów z gośćmi
     */
    protected final List<Table> tables;

    /**
     * Kolejne stany drzewa
     */
    protected List<State> childrenStates;

    /**
     * Lista kolejnych gości do obsadzenia
     */
    protected List<Vertex> possibleChildrenStatesVertices;
    
    /**
     * Informacja, czy w tym stanie gość już został obsadzony
     */
    protected boolean guestPlaced;
    
    /**
     * Informacja czy stan jest finalny
     */
    protected boolean isStateFinal;
    
    /**
     * Poprzedni stan
     */
    protected State parentState;

    /**
     * Logger
     */
    protected Logger logger = Logger.getLogger(State.class);
    

    /**
     * Tworzy nowy stan w drzewie
     * 
     * @param graph graf połączeń
     * @param currentVertex aktualnie rozpatrywany wierzchołek grafu
     * @param tables lista stołów z gośćmi
     * @param parentState poprzedni stan
     */
    public State(Graph graph, Vertex currentVertex, List<Table> tables, State parentState) {
        this.stateId = stateCount++;
        this.graph = graph;
        this.currentVertex = currentVertex;
        this.parentState = parentState;
        this.tables = tables;
        this.childrenStates = new ArrayList<State>();
        this.guestPlaced = false;

        // currentVertex musi należeć do tej kopii grafu!!!
        this.currentVertex.markAsVisited();
        this.possibleChildrenStatesVertices = this.graph.getNotVisitedVerticesSortedByDegree(Main.ascendingSort);
        this.isStateFinal = checkIfStateIsFinal();
    }

    /**
     * Sadza gościa przy stole. W razie potrzeby dodaje nowy stół
     * @return informacja, czy został utworzony nowy stół
     */
    public boolean placeCurrentGuest(){
        if(guestPlaced){
            return false;
        }
        
        boolean guestPlacedAtExistingTable = false;
        
        for(Table table : tables){
            if(table.maySeatHere(currentVertex)){
                table.sitHere(currentVertex);
                guestPlacedAtExistingTable = true;
                
                if(table.getNumberOfSittingGuests() == 4){
                    // wyłączamy te wierzchołki z grafu
                    for(Vertex guest : table.getListOfSittingGuests()){
                        guest.exclude();
                    }
                }
                
                break;
            }
        }
        
        if(!guestPlacedAtExistingTable){
            // nie było gdzie go posadzić - dostawiamy stół
            Table newTable = new Table();
            newTable.sitHere(currentVertex);
            tables.add(newTable);
        }
        
        guestPlaced = true;
        
        return !guestPlacedAtExistingTable;
    }
    
    /**
     * Zwraca informację, czy węzeł może mieć więcej dzieci niż do tej pory
     * @return informacja, czy węzeł może mieć więcej dzieci niż do tej pory
     */
    public boolean mayHaveMoreChildrenStates(){
//        logger.debug("possibleChildrenStatesVertices size: "+possibleChildrenStatesVertices.size());
        return (possibleChildrenStatesVertices.size() != 0);
    }
    
    
    /**
     * Tworzy nowy stan
     * @return nowoutworzony stan
     */
    public State createNewChildState(){
        if(!mayHaveMoreChildrenStates()){
            throw new RuntimeException("This node (vertex: "+ currentVertex.getId() +") cannot have more children!");
        }
        
        Vertex nextVertex = possibleChildrenStatesVertices.get(0);
        possibleChildrenStatesVertices.remove(0);
        
        // kopia grafu
        Graph graphCopy = graph.getDeepCopy();
        // przekazywany wierzchołek musi należeć do kopii grafu
        nextVertex = graphCopy.getVertex(nextVertex.getId());
        
        // kopia stołów
        List<Table> tablesCopy = new ArrayList<Table>();
        for(Table table : tables){
            tablesCopy.add(table.getTableCopy(graphCopy));
        }
        
        // nowy stan
        State newChildState = new State(graphCopy, nextVertex, tablesCopy, this);
        this.childrenStates.add(newChildState);
        
        return newChildState;
    }
    
    
    /**
     * Zwraca informację, czy stan jest finalny, a więc czy wszyscy goście zostali
     * już obsadzeni
     * @return true jeżeli stan jest finalny, false w przeciwnym przypadku
     */
    public boolean isStateFinal(){
        return isStateFinal;
    }
    
    
    /**
     * Zwraca listę stołów z obsadzonymi gośćmi w tym stanie
     * @return lista stołów
     */
    public List<Table> getTables(){
        return tables;
    }
    
    
    /**
     * Zwraca ilość stołów
     * @return ilość stołów
     */
    public int getTablesNumber(){
        return tables.size();
    }
    
    
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        sb.append("stateId: " + stateId);
        sb.append(", parentStateId: " + ((parentState != null) ? parentState.stateId : "null"));
        sb.append(", vertex: " + currentVertex.getId());
        sb.append(", isStateFinal: " + isStateFinal);
        sb.append(", tables: " + tables.size());
        sb.append("]");
        return sb.toString();
    }
    
    /**
     * Sprawdza, czy stan jest finalny
     * @return true jeżeli stan jest finalny, false w przeciwnym przypadku
     */
    protected boolean checkIfStateIsFinal(){
        for(Vertex v : graph.getVertices().values()){
             if(!v.isVisited()){
//                 logger.debug("[checkIfStateIsFinal] NOT final, not visited vertex: " + v.getId());
                 return false;
             }
        }
        
//        logger.debug("[checkIfStateIsFinal] FINAL");
        return true;
    }
    
    
    /**
     * Zwraca liczbę posadzonych już gości
     * @return liczba posadzonych gości
     */
    protected int getNumberOfSittingGuests(){
        int number = 0;
        for(Table t : tables){
            number += t.getNumberOfSittingGuests();
        }
        return number;
    }
}
